package net.wicp.tams.common.others;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.tmatesoft.svn.core.SVNCommitInfo;
import org.tmatesoft.svn.core.SVNDepth;
import org.tmatesoft.svn.core.SVNException;
import org.tmatesoft.svn.core.SVNNodeKind;
import org.tmatesoft.svn.core.SVNURL;
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
import org.tmatesoft.svn.core.internal.wc.DefaultSVNOptions;
import org.tmatesoft.svn.core.io.SVNRepository;
import org.tmatesoft.svn.core.io.SVNRepositoryFactory;
import org.tmatesoft.svn.core.wc.SVNClientManager;
import org.tmatesoft.svn.core.wc.SVNDiffClient;
import org.tmatesoft.svn.core.wc.SVNRevision;
import org.tmatesoft.svn.core.wc.SVNStatus;
import org.tmatesoft.svn.core.wc.SVNUpdateClient;
import org.tmatesoft.svn.core.wc.SVNWCUtil;

import lombok.extern.slf4j.Slf4j;
import net.wicp.tams.common.Result;
import net.wicp.tams.common.constant.DateFormatCase;

@Slf4j
public class SVNUtil {
	private static volatile SVNUtil INSTANCE;
	private Map<String, SVNClientManager> managerMap = new HashMap<>();

	private String[] ignorefiles = new String[] { ".project", ".classpath" };

	private String[] ignoreDirs = new String[] { "target", ".settings", ".svn", "bin" };

	private SVNUtil() {
		// 初始化版本库
		DAVRepositoryFactory.setup();
		SVNRepositoryFactoryImpl.setup();
		FSRepositoryFactory.setup();
		log.info("创建SVNUtil实例成功");
	}

	// 验证登录svn
	public SVNClientManager authSvn(String svnRoot, String username, String password) {
		if (managerMap.get(svnRoot) != null) {
			return managerMap.get(svnRoot);
		}
		// 创建库连接
		SVNRepository repository = null;
		try {
			repository = SVNRepositoryFactory.create(SVNURL.parseURIEncoded(svnRoot));
		} catch (SVNException e) {
			log.error(e.getErrorMessage().getMessage(), e);
			return null;
		}

		// 身份验证
		ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(username,
				password.toCharArray());

		// 创建身份验证管理器
		repository.setAuthenticationManager(authManager);

		DefaultSVNOptions options = SVNWCUtil.createDefaultOptions(true);
		SVNClientManager clientManager = SVNClientManager.newInstance(options, authManager);
		managerMap.put(svnRoot, clientManager);
		return clientManager;
	}

	public static final SVNUtil getInstance() {
		if (INSTANCE == null) {
			synchronized (SVNUtil.class) {
				if (INSTANCE == null) {
					INSTANCE = new SVNUtil();
				}
			}
		}
		return INSTANCE;
	}

	// Make directory in svn repository
	public SVNCommitInfo makeDirectory(SVNClientManager clientManager, SVNURL url, String commitMessage) {
		try {
			return clientManager.getCommitClient().doMkDir(new SVNURL[] { url }, commitMessage);
		} catch (SVNException e) {
			log.error(e.getErrorMessage().getMessage(), e);
		}
		return null;
	}

	/**
	 * Imports an unversioned directory into a repository location denoted by a
	 * destination URL
	 * 
	 * @param clientManager
	 * @param localPath
	 *            a local unversioned directory or singal file that will be
	 *            imported into a repository;
	 * @param dstURL
	 *            a repository location where the local unversioned
	 *            directory/file will be imported into
	 * @param commitMessage
	 * @param isRecursive
	 *            递归
	 * @return obj
	 */
	public SVNCommitInfo importDirectory(SVNClientManager clientManager, File localPath, SVNURL dstURL,
			String commitMessage, boolean isRecursive) {
		try {
			return clientManager.getCommitClient().doImport(localPath, dstURL, commitMessage, null, true, true,
					SVNDepth.fromRecurse(isRecursive));
		} catch (SVNException e) {
			log.error(e.getErrorMessage().getMessage(), e);
		}
		return null;
	}

	/**
	 * Puts directories and files under version control
	 * 
	 * @param clientManager
	 *            SVNClientManager
	 * @param wcPath
	 *            work copy path
	 */
	public void addEntry(SVNClientManager clientManager, File wcPath) {
		try {
			clientManager.getWCClient().doAdd(new File[] { wcPath }, true, false, false, SVNDepth.INFINITY, false,
					false, true);
		} catch (SVNException e) {
			log.error(e.getErrorMessage().getMessage(), e);
		}
	}

	/**
	 * Collects status information on a single Working Copy item
	 * 
	 * @param clientManager
	 * @param wcPath
	 *            local item's path
	 * @param remote
	 *            true to check up the status of the item in the repository,
	 *            that will tell if the local item is out-of-date (like '-u'
	 *            option in the SVN client's 'svn status' command), otherwise
	 *            false
	 * @return obj
	 */
	public SVNStatus showStatus(SVNClientManager clientManager, File wcPath, boolean remote) {
		SVNStatus status = null;
		try {
			status = clientManager.getStatusClient().doStatus(wcPath, remote);
		} catch (SVNException e) {
			log.error(e.getErrorMessage().getMessage(), e);
		}
		return status;
	}

	// Commit work copy's change to svn
	public Result commit(SVNClientManager clientManager, File wcPath, boolean keepLocks, String commitMessage) {
		try {
			this.checkVersiondDirectory(clientManager, wcPath);
			clientManager.getCommitClient().doCommit(new File[] { wcPath }, keepLocks, commitMessage, null, null, false,
					false, SVNDepth.INFINITY);
			return Result.getSuc();
		} catch (SVNException e) {
			return Result.getError(e.getMessage());
		}
	}

	/**
	 * Updates a working copy (brings changes from the repository into the
	 * working copy).
	 * 
	 * @param clientManager
	 *            clientManager
	 * @param wcPath
	 *            working copy path
	 * @param updateToRevision
	 *            revision to update to
	 * @param depth
	 *            update的深度：目录、子目录、文件
	 * @return obj
	 */
	public long update(SVNClientManager clientManager, File wcPath, SVNRevision updateToRevision, SVNDepth depth) {
		SVNUpdateClient updateClient = clientManager.getUpdateClient();

		/*
		 * sets externals not to be ignored during the update
		 */
		updateClient.setIgnoreExternals(false);

		/*
		 * returns the number of the revision wcPath was updated to
		 */
		try {
			return updateClient.doUpdate(wcPath, updateToRevision, depth, false, false);
		} catch (SVNException e) {
			log.error(e.getErrorMessage().getMessage(), e);
		}
		return 0;
	}

	/**
	 * recursively checks out a working copy from url into wcDir
	 * 
	 * @param clientManager
	 * @param svnPath
	 *            a repository location from where a Working Copy will be
	 *            checked out
	 * @param revision
	 *            the desired revision of the Working Copy to be checked out
	 * @param destPath
	 *            the local path where the Working Copy will be placed
	 * @param depth
	 *            checkout的深度，目录、子目录、文件
	 * @return obj
	 */
	public long checkout(SVNClientManager clientManager, String svnPath, SVNRevision revision, File destPath,
			SVNDepth depth) {

		SVNUpdateClient updateClient = clientManager.getUpdateClient();
		/*
		 * sets externals not to be ignored during the checkout
		 */
		updateClient.setIgnoreExternals(false);
		/*
		 * returns the number of the revision at which the working copy is
		 */
		try {
			return updateClient.doCheckout(packUrl(svnPath), destPath, revision, revision, depth, false);
		} catch (SVNException e) {
			log.error(e.getErrorMessage().getMessage(), e);
		}
		return 0;
	}

	// 把当前目录的文件强制更新到其它分支
	public Result overWrite(SVNClientManager clientManager, File workDir, String svnUrl) {
		String workDirPath = System.getProperty("user.home");
		File homeDir = new File(String.format("%s/%s/%s", workDirPath, "/.zhongan/", workDir.getName()));
		try {
			if (homeDir.exists()) {
				FileUtils.forceDelete(homeDir);
			}
			// checkout
			INSTANCE.checkout(clientManager, svnUrl, SVNRevision.HEAD, homeDir, SVNDepth.fromRecurse(true));
			// 复制到home
			FileUtils.copyDirectory(workDir, homeDir, new FileFilter() {

				@Override
				public boolean accept(File pathname) {
					return !isIgnorefiles(pathname);
				}
			});
		} catch (IOException e) {
			log.error("合并代码错误", e);
			return Result.getError("合并代码错误");
		}
		Result retinfo = INSTANCE.commit(clientManager, homeDir, true,
				"合并SVN，时间:" + DateFormatCase.yyyyMMddHHmmss.getInstanc().format(new Date()));

		return retinfo;
		// 不做新分支的切换 切换到新分支
		// INSTANCE.doSwitch(clientManager, workDir, svnUrl);
	}

	// 合并分支
	public Result merge(SVNClientManager clientManager, File workDir, String svnUrl, boolean commit) {
		String workDirPath = System.getProperty("user.home");
		File homeDir = new File(String.format("%s/%s/%s", workDirPath, "/.zhongan/", workDir.getName()));// trunk目录+
																											// "_"
																											// +
																											// UUIDGenerator.getUniqueLong()

		try {
			try {
				FileUtils.forceDelete(homeDir);
			} catch (Exception e) {
			}
			// checkout
			INSTANCE.checkout(clientManager, svnUrl, SVNRevision.HEAD, homeDir, SVNDepth.fromRecurse(true));
			SVNDiffClient diff = clientManager.getDiffClient();

			diff.doMerge(homeDir, SVNRevision.HEAD, workDir, SVNRevision.HEAD, homeDir, SVNDepth.fromRecurse(true),
					false, false, false, false);
		} catch (Exception e) {
			log.error("合并代码错误", e);
			return Result.getError("合并代码错误");
		}
		if (commit) {
			Result retinfo = INSTANCE.commit(clientManager, homeDir, true,
					"合并SVN，时间:" + DateFormatCase.yyyyMMddHHmmss.getInstanc().format(new Date()));
			return retinfo.setRetObjs(homeDir);
		} else {
			return Result.getSuc().setRetObjs(homeDir);
		}
	}

	// 确定path是否是一个工作空间
	public boolean isWorkingCopy(File path) {
		if (!path.exists()) {
			log.warn("'" + path + "' not exist!");
			return false;
		}
		try {
			if (null == SVNWCUtil.getWorkingCopyRoot(path, false)) {
				return false;
			}
		} catch (SVNException e) {
			log.error(e.getErrorMessage().getMessage(), e);
		}
		return true;
	}

	// 确定一个URL在SVN上是否存在
	public boolean isURLExist(SVNClientManager clientManager, String svnPath) {
		try {
			SVNRepository svnRepository = clientManager.createRepository(packUrl(svnPath), true);
			SVNNodeKind nodeKind = svnRepository.checkPath("", -1);
			return nodeKind == SVNNodeKind.NONE ? false : true;
		} catch (SVNException e) {
			e.printStackTrace();
		}
		return false;
	}

	private SVNURL packUrl(String svn) throws SVNException {
		return SVNURL.parseURIEncoded(svn);
	}

	public long doSwitch(SVNClientManager clientManager, File destPath, String svnUrlStr) {
		SVNUpdateClient updateClient = clientManager.getUpdateClient();
		updateClient.setIgnoreExternals(false);
		try {
			return updateClient.doSwitch(destPath, packUrl(svnUrlStr), SVNRevision.HEAD, SVNRevision.HEAD,
					SVNDepth.fromRecurse(false), false, false);
		} catch (SVNException e) {
			return -1L;
		}
	}

	public void checkVersiondDirectory(SVNClientManager clientManager, File wc) {
		if (!SVNWCUtil.isVersionedDirectory(wc) && !isIgnorefiles(wc)) {
			addEntry(clientManager, wc);
		}
		if (wc.isDirectory()) {
			for (File sub : wc.listFiles()) {
				if (sub.isDirectory() && sub.getName().equals(".svn")) {
					continue;
				}
				checkVersiondDirectory(clientManager, sub);
			}
		}
	}

	// 判断文件是否该忽略
	public boolean isIgnorefiles(File file) {
		if (file == null || !file.exists()) {
			return true;
		}
		boolean isIgnore = false;
		if (file.isDirectory()) {
			String filePath = file.getPath().replace("\\", "/");
			for (String dir : ignoreDirs) {
				if (ArrayUtils.contains(filePath.split("/"), dir)) {
					isIgnore = true;
					break;
				}
			}
		} else {
			isIgnore = ArrayUtils.contains(ignorefiles, file.getName());
		}
		return isIgnore;
	}

}
